#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Reference.h"

#include "Class.h"
#include "ObjectTable.h"
#include "String.h"
#include "Exception.h"

//---- reference ---------------------------------------------------------------

NewMetaImpl(Ref, Object, (T(type), T(offset), T(len), TP(cla), TX(base)));

Ref::Ref()
{
	base= 0;
	type= offset= 0;
	len= -1;
	cla= 0;
}

Ref::Ref(void *b, int t, int o, int l, Class *c, bool g, bool lg)
{
	base= b;
	type= t;
	offset= o;
	len= l;
	cla= c;
	SetFlag(eRefGlobal, g);
	SetFlag(eRefLenGlobal, lg);
}

Ref::Ref(Object &op)
{
	base= &op;
	type= offset= 0;
	len= -1;
	cla= op.IsA();
}

Ref::Ref(Object *&op)
{
	base= &op;
	type= T_PTR;
	offset= 0;
	len= -1;
	cla= op->IsA();
}

Ref::Ref(const Ref &ref)
{
	base= ref.base;
	type= ref.type;
	offset= ref.offset;
	len= ref.len;
	cla= ref.cla;
}

void Ref::operator=(Ref ref)
{
	base= ref.base;
	type= ref.type;
	offset= ref.offset;
	len= ref.len;
	cla= ref.cla;
}

bool Ref::IsEqual(Object *op)
{
	if (op->IsKindOf(Ref))
		return (((Ref*)op)->base == base) && (((Ref*)op)->offset == offset);
	return FALSE;
}

bool Ref::IsObject()
{
	if (type & (T_ARR|T_VEC|T_PTR2))
		return FALSE;
	return cla && cla->TestFlag(eClassObject) && (type == 0)
									&& ObjectTable::PtrIsValid((Object*)base);
}

void *Ref::Addr()
{
	if (IsGlobal())
		return (void*) offset;
	return (void*) ((u_long)base + (u_long)offset);
}

int Ref::Size()
{
	if (type == 0 && cla)
		return cla->Size();
	return sizeof(void*);
}

int Ref::Length()
{
	int l= 0;

	if (type & T_ARR)
		return len;
	if (type & T_VEC) {
		int *ll;
		if (TestFlag(eRefLenGlobal))
			ll= (int*) len;
		else
			ll= (int*) ((u_long) base + (u_long) len);

		TRY {
			l= *ll;
		} CATCH(ex) {
			return -2;
		} ENDTRY

		return l;
	}
	if (type & T_STR) {
		int s;
		if ((type & ~T_STR) == 0 && cla)
			s= cla->Size();
		else
			s= sizeof(void*);
		void *val= (void*) *((u_long*) Addr());
		if (val == 0)
			return -1;
		TRY {
			switch (s) {
			case 1:
				{
					char *cp= (char*)val;
					while (*cp++)
						l++;
				}
				break;
			case 2:
				{
					short *sp= (short*)val;
					while (*sp++)
						l++;
				}
				break;
			case 4:
				{
					int *ip= (int*)val;
					while (*ip++)
						l++;
				}
				break;
			}
		} CATCH(ex) {
			return -2;
		} ENDTRY
		return l;
	}
	return -1;
}

Ref *Ref::Deref()
{
	void *oo= 0, *oo2= 0;

	if (type == 0) {    // simple
		if (IsObject())
			return new Ref(Addr(), type, 0, Length(), cla);
		return 0;
	}

	if (type & T_ARR)   // inline array of simple
		return new Ref(Addr(), type & ~T_ARR, 0, Length(), cla);

	TRY {
		oo= (void*) *((int*)Addr());
	} CATCH(ex) {
		return 0;
	} ENDTRY

	TRY {
		oo2= (void*) *((int*)oo);
	} CATCH(ex) {
		return 0;
	} ENDTRY

	if (oo == 0)
		return 0;

	if (type & (T_VEC|T_STR|T_PTR))
		return new Ref(oo, type & ~(T_VEC|T_STR|T_PTR), 0, Length(), cla);

	if (type & T_PTR2)
		return new Ref(oo, type & ~T_PTR2, 0, -1, cla);

	return 0;   // cannot happen
}

char *Ref::Value(char *name)
{
	int l;
	void *val;
	bool isptr= TRUE,
		getvalue= FALSE;
	char *pre= "  ",
		*post,
		buf[400],
		*bp;

	if ((type & T_PTR) && (type & T_PTR2))
		pre= "**";
	else if ((type & T_PTR) || (type & T_PTR2))
		pre= " *";

	switch (l= Length()) {
	case -2:
		post= "[<ill addr>]";
		break;
	case -1:
		post= "";
		break;
	default:
		post= form("[%d]", l);
		break;
	}

	memset(buf, 0, 400);
	bp= buf;
	val= Addr();

	if (type & T_ARR) {
		strcpy(bp, "<inline> ");
		bp+= strlen(bp);
		if (! (type & T_PTR2))
			getvalue= TRUE;
	} else if (type == 0) {
		isptr= FALSE;
		getvalue= TRUE;
	} else {
		val= *((void**) val);
		if (val) {
			strcpy(bp, form("0x%08x ", val));
			bp+= strlen(bp);
			if ((type & (T_PTR|T_VEC|T_STR)) && !(type & T_PTR2) || (type == T_PTR2))
				getvalue= TRUE;
		} else
			strcpy(bp, "<nil>");
	}

	if (getvalue) {
		TRY {
			cla->Show(bp, val, l, isptr);
		} CATCH(ex) {
			strcat(bp, "<illegal address>");
		} ENDTRY
	}

	return form("  %-14.14s %1s%-18.18s: %s",
					cla->Name(),
					pre,
					form("%s%s%s ", IsGlobal() ? "::" : "", name, post),
					buf);
}

